package com.github.codemperor.core.lock;

import com.aliyun.oss.common.utils.StringUtils;
import com.github.codemperor.core.lock.bean.LockResult;
import com.github.codemperor.core.lock.redis.LockContext;

import java.util.function.Consumer;

/**
 * 锁的操作
 */
public class FunnyLock {
    private FunnyLock(LockConfiguration config) {
        this.config = config;
    }

    public static FunnyLock getInstance(LockConfiguration config) {
        if (StringUtils.isNullOrEmpty(config.getLockKey())) {
            return null;
        }
        return new FunnyLock(config);
    }

    public static FunnyLock getInstance(String lockKey) {
        return getInstance(new LockConfiguration().setLockKey(lockKey));
    }

    public static FunnyLock getInstance(String lockKey, boolean waitingEnabled) {
        return getInstance(new LockConfiguration().setLockKey(lockKey).setWaitingEnabled(waitingEnabled));
    }

    private LockConfiguration config;

    /**
     * 设置锁是否成功, 成功代表之前没锁，失败代表之前有锁, 默认设置失败，防止不调用lock直接调用service
     */
    private boolean isLockSuccess = false;

    /**
     * 这里加入service已执行标记，防止用户链式调用重新执行service。这样在多线程中会不断waiting，消耗性能，扰乱锁
     */
    private boolean serviceExecuted = false;

    /**
     * 返回值
     */
    private LockResult result = LockResult.OK; //默认执行成功

    public LockResult getResult() {
        return result;
    }

    /**
     * 加锁
     *
     * @return
     */
    public FunnyLock lock() {
        this.isLockSuccess = LockContext.setnx(config.getLockKey(), config.getExpireSec());
        return this;
    }

    /**
     * 执行业务的核心方法
     * 依据此方法进行封装，对外提供业务执行的接口
     *
     * @param function   业务回调
     * @param autoUnlock 是否自动解锁
     */
    private void serviceProcess(Consumer<FunnyLock> function, boolean autoUnlock) {
        if (serviceExecuted) { //判定此类是否已开启业务执行，开启后不允许多次执行
            return;
        }
        serviceExecuted = true;
        // 设置锁成功，可以执行业务
        if (isLockSuccess) {
            function.accept(this);
            result = LockResult.OK;
            if (autoUnlock) { //判定是否进行自动解锁
                unLock();
            }
        } else {
            // 设置锁失败，之前有未过期的锁，线程等待并重试
            waiting(function);
        }
    }

    /**
     * 加锁的业务处理lambda， 只有此方法参与lock
     * 注意，链式调用中，只允许调用一次，并且返回lock实体，用户需要自己解锁unlock!!!!!!
     *
     * @return
     */
    public FunnyLock service(Consumer<FunnyLock> function) {
        serviceProcess(function, false);
        return this;
    }

    /**
     * 此方法自动帮助业务解锁，无需调用unlock方法
     * !!!!!!注意：只有同步业务才可以使用自动解锁功能!!!!!!!
     * @return
     */
    public FunnyLock serviceAndUnlockIfSuccess(Consumer<FunnyLock> function) {
        serviceProcess(function, true);
        return this;
    }


    /**
     * 扩展方法，方便链式调用, 不参与锁
     * todo 待完善
     *
     * @param function
     * @return
     */
    public FunnyLock extend(Consumer<FunnyLock> function) {
        function.accept(this);
        return this;
    }

    /**
     * 解锁
     * 推荐用户需要在service中自行解锁，而不是框架自动在service最后解锁。
     *
     * @return
     */
    public void unLock() {
        //这里进行解锁
        LockContext.remove(config.getLockKey());
    }

    /**
     * 自动过期以及线程等待的时间振动器
     */
    private void waiting(Consumer<FunnyLock> function) {
        // 未开启线程重试，直接return
        if (!config.isWaitingEnabled()) {
            result = LockResult.FAILED;
            return;
        }

        int waitingMS = config.getWaitingSec() * 1000; //获取等待时长，转换为ms

        // 设置震动阈值，不要太低，会极大消耗性能，太高则会影响业务的执行频次
        int sleep = 100;
        while (waitingMS > 0) {
            // 如果锁已经被释放
            if (!LockContext.isLock(config.getLockKey())) {
                lock(); //上锁
                if (isLockSuccess) {
                    function.accept(this);
                    result = LockResult.OK_EXPIRED;
                    break;
                }
            }

            result = LockResult.FAILED_EXPIRED;
            waitingMS -= sleep;
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
